iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
自我挑戰組

Data Engineer 下班後偷學 AI系列 第 8

RAG 還有必要嗎?

  • 分享至 

  • xImage
  •  

Context 都這麼大了,為什麼還需要 RAG?
這篇會用《寶可夢十八氣》當知識庫,快速走過 RAG 從 Chunking、Embedding 到 Retrieval 的基本流程。

從 RAG 到 LLM Wiki

LLM 最大的問題之一,是它其實「不知道自己不知道什麼」。它只是根據使用者的輸入,預測下一個最可能出現的 token,所以本質上並不真正理解自己在說什麼,也因此可能出現憑空捏造答案的情況,也就是所謂的 Hallucination。

而 RAG 做的事情,就是先找出 LLM 回答問題時可能需要的資訊,再把這些資訊一併放進 Context,讓模型在回答時有額外的依據,而不是只靠模型本身既有的知識去猜。

但 RAG 也不是萬能的,就最普通的 RAG 來說,雖然能找到直接相關的資訊,但有可能會漏掉整個資料集的 global questions,這也是為什麼會有像 graphRAG 等技術出現,但同樣的問題,現在 AI 模型的上下文窗口動輒百萬 tokens,之後也只會越來越大,全部塞進去不行嗎?幹嘛還要花時間做 RAG 再去找相關資訊?全部塞進去就不需要煩惱什麼關係鏈的遺失啦?

答案是 RAG 還是有存在的必要,一來是能不能塞進去完全取決於文件數量,二來是就算全部塞進去,模型的注意力反而可能因此被分散,所以「模型裝得下多少資料」和「模型能不能有效利用這些資料」根本是兩回事,所以真正的問題是那些資訊應該進入 Context?

對於 Context Engineering 來說,RAG 只是其中一種手段,更新的做法是讓 LLM 幫我們管理知識的 LLM-based Knowledge Management。像是 Karpathy 就提出 llm-wiki 的概念,直接請 LLM agent 維護一個知識 Wiki。但就現階段而言,這表示我們必須相信 LLM 每一次都能正確判斷「我要找什麼、去哪裡找,以及什麼資訊值得保留」,所以可能還不太適合用在生產環境中,而 RAG 仍然有其不可替代的價值。

RAG 的基本流程

1. 準備文件

《寶可夢十八氣》是我長期觀察寶可夢屬性與人的狀態後,慢慢整理出來的一套方法。
我發現十八種屬性,不只可以拿來對戰,也很適合解釋人的情緒、行動力、界線與人生狀態。
既然有幸悟到這套東西,我也不打算藏私。
所以把它完整整理下來,傳給所有有緣的訓練師。

2. Chunking

幾種常見的 Chunking 策略:

2.1 Fixed-size Chunking

用固定的文字長度 (或 token 長度) 來切,最簡單,但也最沒有語意結構,拉完了。

2.2 Sliding Window / Overlap Chunking

Fixed-size Chunking 讓每個 Chunk 之間 overlap,減少上下文直接從中間切斷的問題,但一樣是拉完了。

2.3 Recursive Chunking

優先按照文件本身比較自然的邊界切割,ex: \n\n,如果還是太大,再往更細的單位切,ex: \n,舉例來說 LangChainRecursiveCharacterTextSplitter

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=[ "\n\n", "\n", "。", " ", "" ]
)
chunks = splitter.split_text(text)

2.4 Structure-aware Chunking

直接利用文件本身的結構,非常適合用在 markdown 這種有固定結構的文件,其他還有像 html, pdf, 或甚至程式碼,都有一套結構可以利用,這些結構本身就帶有一種主題分類的概念。

觀察 寶可夢十八氣.md 的文章結構,發現每個主要段落間都有用 --- 來分隔,真是太貼心了。

with open("寶可夢十八氣.md", encoding="utf-8") as f:
    raw_text = f.read()

chunks = raw_text.split("---")

2.5 Semantic Chunking

只有文件格式怎麼夠,我要用語意分的乾乾淨淨啊,我們可以計算每個句子 (段落) 的 Embedding 間的 Similarity 的變化,如果 Similarity 突然變低那大概就是一個斷點,或者我們也可以讓 LLM 來替我們切 Chunks。

3. Embedding

Embedding 是把文字轉成可以搜尋的語意座標 (高維度空間中的一個點),這些 Embedding Models 在經過大量的資料訓練後,輸出的向量竟然能神奇的反映文字的語意,而語意相似的文字,在 Vector Space 裡應該彼此靠近。

不論輸入的文字多長,同一個模型輸出的向量長度都相同,但不同 Embedding 模型的向量長度可能不同,所代表的意義也是完全不同的。

embedding_model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
embeddings = embedding_model.encode(chunks)
dimension = len(embeddings[0])

print(f"Length: {dimension}")
print(embeddings[0])

輸出是:

Length: 384
[ 1.29806727e-01  2.08567604e-01 -8.23545754e-02  1.55131847e-01 ...]

4. Vector Database

文件轉成 Vector 後需要找的地方存起來,這些 Vector DB 通常都經過特殊設計,能使用 Approximate Nearest Neighbor(ANN) Index 來快速找到相似的 Vector,能省下一筆一筆算 Cosine Similarity 的時間。另外除了存 Vector 外,也會存原始文字和其他的 metadata,在搜尋時可以同時用 Vector Similarity + Metadata Filtering 來大大地加速並縮小範圍。

但這邊為了方便用的是 Milvus Lite,是沒有 ANN 的:

from pymilvus import MilvusClient

milvus_client = MilvusClient("rag.db")

milvus_client.create_collection(
    collection_name="documents",
    dimension=dimension
)

def to_doc(id, vector, text):
    return {
        "id": id, 
        "vector": vector,
        "text": text
    }

documents = [to_doc(i+1, embedding, chunk) for i, (embedding, chunk) in enumerate(zip(embeddings, chunks))]
milvus_client.insert(
    collection_name="documents",
    data=documents
)

5. Retrieval

資料存進 milvus 後,下次使用前要先 load_collection 後才能用:

milvus_client.load_collection(
    collection_name="documents"
)

milvus 中搜索相似資料:

def RAG(text, top_k=3):
    results = milvus_client.search(
        collection_name="documents",
        data=[embedding_model.encode(text)],
        limit=top_k,
        output_fields=["text"]
    )

    return [result["entity"]["text"] for result in results[0]]

6. Augmented Generation

系統提示詞:

你是一位精通《寶可夢十八氣》的命理分析師。

請優先根據提供的 RAG 資料,用《寶可夢十八氣》分析使用者的狀況,避免寫成一般心理諮詢。

分析時:

* 判斷最相關的氣,以及較像氣旺、氣衰、氣滯或氣洩。
* 資料足夠時,再分析主位、佐位、調候位。
* 自然加入 2~3 隻寶可夢作為有趣比喻,但不要捏造知識庫中不存在的規則。
* 如果資料不足,仍根據現有資訊做合理推測,並清楚標示哪些是推測。
* 最後簡短說明還需要哪些資訊,才能進一步分析。

回答使用繁體中文,一本正經但可以帶少量幽默。
請保持精簡,控制在 300~500 字左右,避免重複解釋與過長的條列。

實際跑看看:

question = """
最近每天上班都覺得很累,工作內容也越做越煩,常常一想到明天還要上班就很抗拒。
我其實已經很想離職了,但又會擔心離職之後找不到更好的工作,所以一直拖著沒有行動。
我該怎麼做?
"""

client = OpenAI(
    api_key=API_KEY,
    base_url=BASE_URL
)

rags = RAG(question)
rag_prompt = "\n\n".join(rags)
content = f"問題:\n{question}\n\n請參考以下RAG 檢索到的資料回答問題,不要憑記憶或上網搜尋:\n\n{rag_prompt}"

response = client.responses.create(
    model=MODEL,
    input=content,
    instructions=SYSTEM_PROMPT
)

print(response.output_text)

輸出結果:

依《寶可夢十八氣》,你目前較像**草衰伴隨火滯,並可能有長期氣洩**;這是依現有描述的推測,不是醫療診斷。

**草衰**:每天疲累、工作越做越煩,像妙蛙種子持續被要求出力,卻少有滋養。若也感到學不到東西、看不見成長,便更符合草衰。

**火滯**:你已有離職念頭,卻被「找不到更好工作」卡住,比起沒志氣,更像小火龍有火卻難以發動。資料提醒須兼察其他氣,但目前不足以細判,也不宜硬定主位、佐位、調候位。

眼下宜**先養草,再讓火有小出口**:
- 暫不逼自己立即辭職,也別靠提神或責罵自己「不夠努力」繼續硬撐。
- 先減少可避免的加班,保留休息,辨認最耗損你的究竟是工作量、人際,還是缺乏成長。
- 把離職拆成小步:盤點存款與必要支出、更新履歷、少量投遞,用實際職缺與面試回饋降低未知,不必一下變成衝刺的風速狗。

要進一步辨氣,還需知道:疲累持續多久、休假能否恢復、睡眠如何,以及最早讓你開始抗拒工作的事件。關鍵不是催你走,而是找出最先失衡的一環。

每日一句

我的 Context 已經滿了,請不要再塞工作進來。


上一篇
零資料保留 Zero Data Retention (ZDR)
系列文
Data Engineer 下班後偷學 AI8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言